package br.ufpe.cin.mac.middleware.components.concrete;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.util.Iterator;
import java.util.List;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import br.ufpe.cin.mac.middleware.components.ConcreteComponent;
import br.ufpe.cin.mac.util.Message;

public class Crypto extends ConcreteComponent implements CryptoMBean {

	public Crypto() {
		super(Crypto.class.getCanonicalName());

	}

	public Message cryptography(Message msg) {
		try {
			PublicKey pub = carregaChavePublica(new File("chave.publica"));			
			byte [] criptText;			
			byte[] chaveCifrada;
			// Gerando uma chave simetrica de 128 bits
			KeyGenerator kg = KeyGenerator.getInstance("AES");
			kg.init(128);
			SecretKey sk = kg.generateKey();
			byte[] chave = sk.getEncoded();
			
			// Criptografando bMethos e inserindo na mensagem
			byte[] bMethod = msg.getBMethod();
			criptText = cifra(pub, bMethod, chave);						
			msg.setBMethod(criptText);
			bMethod = null;
						
			// Criptografando cada bParam e inserindo-os na mensagem			
			byte[][] bParam = msg.getBParam();
			for(int i = 0 ; i < bParam.length; i++){
				criptText = cifra(pub, bParam[i], chave);				
				bParam[i] = criptText;
			}			
			msg.setBParam(bParam);
			
			
			// Criptografando cada bSig e inserindo-os na mensagem			
			byte[][] bSig = msg.getBSig();
			for(int i = 0 ; i < bSig.length; i++){
				criptText = cifra(pub, bSig[i], chave);				
				bSig[i] = criptText;
			}			
			msg.setBSig(bSig);
			
			// Cifrando a chave com a chave p�blica
			Cipher rsacf = Cipher.getInstance("RSA");
			rsacf.init(Cipher.ENCRYPT_MODE, pub);
			chaveCifrada = rsacf.doFinal(chave);
			msg.setKey(chaveCifrada);
			
			return msg;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	protected void msgManager(Message msg) {
		List<String> ops = msg.readOperations();
		Iterator<String> it = ops.iterator();
		while (it.hasNext()) {

			String op = (String) it.next();
			if (op.equals("sendResponse"))
				sendResponse(msg);

			else if (op.equals("sendRequest"))
				sendRequest(msg);

			else if (op.equals("Cryptography"))
				cryptography(msg);
		}
	}

	private void sendResponse(Message msg) {
		msg = this.cryptography(msg);
		buffer.put(msg.hashCode(), msg);
		sendNotification(msg, "terP");
	}

	private void sendRequest(Message msg) {
		msg = this.cryptography(msg);
		buffer.put(msg.hashCode(), msg);
		sendNotification(msg, "invU");
	}
	
	
	/**
	 * Cifra um bloco de dados com a chave p�blica.
	 *
	 * @param pub
	 *            A chave p�blica.
	 * @param textoClaro
	 *            O bloco de dados a ser cifrado.
	 * @return Um arrays de bytes, que � o bloco cifrado.
	 * 
	 * @throws NoSuchAlgorithmException
	 *             Algoritmo (AES) n�o dispon�vel na sua vers�o do JDK.
	 * @throws NoSuchPaddingException
	 *             Padding (PKCS5Padding) n�o dispon�vel na sua vers�o do JDK.
	 * @throws InvalidKeyException
	 *             Se a chave p�blica for inv�lida.
	 * @throws IllegalBlockSizeException
	 *             N�o deve ocorrer.
	 * @throws BadPaddingException
	 *             N�o deve ocorrer.
	 * @throws InvalidAlgorithmParameterException
	 *             N�o deve ocorrer.
	 */
	public byte[] cifra(PublicKey pub, byte[] textoClaro, byte[] chave)
			throws NoSuchAlgorithmException, NoSuchPaddingException,
			InvalidKeyException, IllegalBlockSizeException,
			BadPaddingException, InvalidAlgorithmParameterException {
		byte[] textoCifrado = null;
					
		// Cifrando o texto com a chave sim�trica gerada
		Cipher aescf = Cipher.getInstance("AES/CBC/PKCS5Padding");
		IvParameterSpec ivspec = new IvParameterSpec(new byte[16]);
		aescf
				.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(chave, "AES"),
						ivspec);
		textoCifrado = aescf.doFinal(textoClaro);		

		return textoCifrado;
	}


	/**
	 * Carrega a chave p�blica serializada de um arquivo.
	 * @param fPub O arquivo com a chave p�blica serializada.
	 * @return A chave p�blica
	 * @throws IOException Se n�o achar o arquivo, ou se houver algum problema
	 * ao efetuar a des-serializa��o.
	 * @throws ClassNotFoundException O objeto contido no arquivo � de uma classe n�o presente
	 * neste projeto.
	 */
	public PublicKey carregaChavePublica(File fPub) throws IOException,
			ClassNotFoundException {
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fPub));
		PublicKey ret = (PublicKey) ois.readObject();
		ois.close();
		return ret;
	}
	
	public static void main(String args[]){		
		Message m = new Message("Sender","Receiver","MSGBody");		
		m.setMethod("carregaChavePublica");
		m.setParameter(new Object[]{new Integer(3), new String("teste")});
		m.setSignature(new String[]{Integer.class.getName(), String.class.getName()});
		
		
		
		Crypto crypto = new Crypto();
		System.out.println("-----------------------------------------");
		System.out.println("-----------------------------------------");
		System.out.println("Message before");
		System.out.println(m.toString());
		System.out.println("\n-----------------------------------------");
		System.out.println("-----------------------------------------");
		crypto.cryptography(m);
		System.out.println("Message crypto");
		System.out.println(m.toString());
		Decrypt decrypt = new Decrypt();
		decrypt.decryptograph(m);
		System.out.println("\n\n-----------------------------------------");
		System.out.println("-----------------------------------------");
		System.out.println("Message after");
		System.out.println(m.toString());
		
	}


}
